using System;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;
using log4net;

namespace AnguaRPGEngine.Library.Sockets
{
    #region ClientSocket Region
    /// <summary>
    /// Handles connecting, disconnecting, and receipt and sending of data. Derived from !BaseSocket.
    /// </summary>
    public class ClientSocket : BaseSocket
    {
        /// <summary>
        /// raised when connection occurs
        /// </summary>
        public event ConnectEventHandler OnConnect;
        /// <summary>
        /// raised when new data is received
        /// </summary>
        public event ReceiveEventHandler OnReceive;
        /// <summary>
        /// raised when the connection is timed out
        /// </summary>
        public event ConnectionTimeoutEventHandler OnConnectionTimeout;
        /// <summary>
        /// Occurs when the IP address of a network interface changes
        /// </summary>
        public event EventHandler OnNetworkAddressChanged;
        private void RaiseNetworkAddressChangedEvent()
        {
            EventHandler handler = OnNetworkAddressChanged;
            if (handler != null)
            {
                OnNetworkAddressChanged(this, null);
            }
        }
        /// <summary>
        /// Occurs when the availability of the network changes
        /// </summary>
        public event EventHandler<NetworkAvailabilityEventArgs> OnNetworkAvailabilityChanged;
        private void RaiseNetworkAvailabilityChangedEvent(NetworkAvailabilityEventArgs e)
        {
            EventHandler<NetworkAvailabilityEventArgs> handler = OnNetworkAvailabilityChanged;
            if (handler != null)
            {
                OnNetworkAvailabilityChanged(this, e);
            }
        }

        private IPEndPoint remoteEP;
        private EndPoint localEP;

        //2008/9/25 - BV - removing the connection timer since all disconnection notifications will be handled by ReceiveCallback method
        //2008/12/01 - ML - adding back in to allow for a connection attempt timeout.
        private System.Timers.Timer connectionTimer;// = new System.Timers.Timer();
        private Socket socket;

        private bool connecting = false;

        private const int ONESECOND = 1000;
        private const int CONNECTION_RESET_BY_PEER = 10054; // Error code for Connection reset by peer

        private ulong keepAliveTimeInMS = 5000; //5 sec
        private ulong keepAliveIntervalInMS = 1000; //1 sec

        //private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        //static int sysCount = 0;                          // Added for testing see if timer is still going off.
        //int count = sysCount++;                           // Increase counter each time a class is created. just for testing. 

        #region Constructor Region
        /// <summary>
        /// constructor: creates a socket object  
        /// </summary>
        public ClientSocket()
            : base()
        {
            //2008/9/25 - BV - removing the connection timer since all disconnection notifications will be handled by ReceiveCallback method
            //SetupTimer(false);
            NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(NetworkChange_NetworkAddressChanged);
            NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
        }

        /// <summary>
        /// constructor: creates a socket object with the newly accepted socket and default buffer size
        /// </summary>
        /// <param name="socket">newly accepted socket</param>
        public ClientSocket(Socket socket)
            : this()
        {
            this.socket = socket;
            //2008/9/25 - BV - removing the connection timer since all disconnection notifications will be handled by ReceiveCallback method
            //SetupTimer(true);
        }

        /// <summary>
        /// constructor: creates a socket object with the newly accepted socket and specified buffer size
        /// </summary>
        /// <param name="socket">newly accepted socket</param>
        /// <param name="bufferSize">user-specified buffer size</param>
        public ClientSocket(Socket socket, int bufferSize)
            : this(socket)
        {
            receiveBufferSize = bufferSize;
        }

        //2008/9/25 - BV - removing the connection timer since all disconnection notifications will be handled by ReceiveCallback method
        //2008/12/01 - ML - adding back in for connection timeout use
        ///// <summary>
        ///// This method creates the Connection timer with the spcified timeout value in sec.
        ///// </summary>
        ///// <param name="enabled"></param>
        private void SetupTimer(int timeout)
        {
            connectionTimer = new System.Timers.Timer(timeout * ONESECOND);
            //connectionTimer.Interval = timeout * ONESECOND;
            connectionTimer.Elapsed += new System.Timers.ElapsedEventHandler(connectionTimer_Elapsed);
            //connectionTimer.Enabled = enabled;
            //log4net.Config.XmlConfigurator.Configure();
        }

        void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
        {
            RaiseNetworkAvailabilityChangedEvent(e);
        }

        void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
        {
            RaiseNetworkAddressChangedEvent();
        }

        #endregion//constructor

        #region StartClient Region
        /// <summary>
        /// initializes the client, remote endPoint and localEndPoint 
        /// </summary>
        /// <param name="host">IP address or name of machine to connect to</param>
        /// <param name="remotePort">remote port to connect to</param>
        /// <param name="localHost">IP address or name of local connection; "" for default connection</param>
        /// <param name="localPort">local port to connect from; 0 for dynamic local port allocation</param>
        public void StartClient(string host, int remotePort, string localHost, int localPort)
        {
            IPAddress ipAddress;
            IPAddress localIpAddress;
            try
            {
                ipAddress = GetIPAddress(host);
                remoteEP = new IPEndPoint(ipAddress, remotePort);
                if (socket != null)//BV - 6/05/2008 - kill the old socket before making a new one
                {
                    try//try doing this in case something goes wrong...
                    {

                        Shutdown();
                        socket = null;
                        GC.Collect();

                    }
                    catch (Exception caught)
                    {
                        //ignore exception and do what we've always done...
                    }
                }
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                SetDefaultSocketOptionsAndProperties();
                //2008/9/25 - BV - removing connection timer
                //connectionTimer.Enabled = true;


                //BV - 2008/10/16 - allowing dynamic port allocation
                //if (localHost != "" && localPort > 0)
                if (localHost != string.Empty) //NOTE: if port is zero .NET will use any available port
                {
                    localIpAddress = GetIPAddress(localHost);
                    localEP = new IPEndPoint(localIpAddress, localPort);
                    socket.Bind(localEP);
                }
                else
                {
                    throw new Exception("ClientSocket cannot StartClient because the localHost string is empty");
                }
            }
            catch (SocketException se)
            {
                RaiseOnSocketError(se);
                localEP = null;
                socket = null;
            }
            catch (Exception e)
            {
                RaiseOnGeneralError(e);
                localEP = null;
                socket = null;
            }
        }

        /// <summary>
        /// initializes the client and remoteEndPoint; uses default connection for localEndPoint
        /// </summary>
        /// <param name="host">IP address or name of machine to connect to</param>
        /// <param name="remotePort">remote port to connect to</param>
        public void StartClient(string host, int remotePort)
        {
            StartClient(host, remotePort, IPAddress.Any.ToString(), 0);//BF# 14756 - BV - 2008/10/23 - use the best ip address by default
        }

        #endregion//start client region

        #region Connection Region
        /// <summary>
        /// raises the OnConnect event when asynchronous connection is complete
        /// </summary>
        /// <param name="ar">IAsyncResult object</param>
        private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                if (connectionTimer != null)
                {
                    connectionTimer.Enabled = false;
                }
                //2008/9/26 - BV - this creates a method specific variable "socket"
                //just like the class level variable "socket"
                //they are one and the same and this duplicate variable is not needed
                //Socket socket = (Socket)ar.AsyncState;
                if (this.socket.Equals((Socket)ar.AsyncState))
                {
                    connecting = false;
                    socket.EndConnect(ar);//this finalizes the connection on our socket
                    if (connectionTimer != null)
                    {
                        connectionTimer.Elapsed -= new System.Timers.ElapsedEventHandler(connectionTimer_Elapsed);
                        connectionTimer = null;
                    }
                    RaiseOnConnect();
                }
                else
                {
                    //some other socket is trying to connect...
                    throw new Exception("IAsyncResult returned a different socket than the one we are trying to connect");
                }
                //2008/9/25 - BV - this isn't used for anything 
                //currentlyConnected = true;


            }
            catch (SocketException se)
            {
                RaiseOnSocketError(se);
            }
            catch (Exception e)
            {
                RaiseOnGeneralError(e);
            }
        }

        /// <summary>
        ///  This does a connection to the host socket.
        /// </summary>
        /// <returns>zero if successful, non-zero if failure</returns>
        public int Connect()
        {
            int rc = 0;
            try
            {
                //BF# 1474 - 200/10/21 - BV 
                //this is already called from StartClient and cannot be called on a socket that is already bound
                //SetDefaultSocketOptionsAndProperties();
                connecting = true;
                socket.Connect(remoteEP);
                if (socket.Connected)//2008/9/25 - BV - we need to notify subscribers when we are connected
                {
                    RaiseOnConnect();
                }
                else
                {
                    //failed to connect for whatever reason
                }

            }
            catch (SocketException se)
            {
                connecting = false;
                RaiseOnSocketError(se);
                rc = -1;
            }
            catch (Exception e)
            {
                connecting = false;
                RaiseOnGeneralError(e);
                rc = -1;
            }
            return rc;
        }

        /// <summary>
        /// begins an asyncronous request for connection
        /// </summary>
        public void StartConnect()
        {
            //try
            //{
            //    connecting = true;
            //    socket.BeginConnect(remoteEP, new AsyncCallback(ConnectCallback), socket);
            //}
            //catch (SocketException se)
            //{
            //    connecting = false;
            //    RaiseOnSocketError(se);
            //}
            //catch (Exception e)
            //{
            //    connecting = false;
            //    RaiseOnGeneralError(e);
            //}
            StartConnect(false);
        }

        private void StartConnect(bool connectTimeoutEnabled)
        {
            try
            {
                connecting = true;
                if (connectionTimer != null)
                {
                    connectionTimer.Enabled = connectTimeoutEnabled;
                }
                socket.BeginConnect(remoteEP, new AsyncCallback(ConnectCallback), socket);
            }
            catch (SocketException se)
            {
                connecting = false;
                RaiseOnSocketError(se);
            }
            catch (Exception e)
            {
                connecting = false;
                RaiseOnGeneralError(e);
            }
        }

        /// <summary>
        /// Begin an asyncronous request for connection with a connection timeout in seconds
        /// </summary>
        /// <param name="connectionTimeout"></param>
        public void StartConnect(int connectionTimeout)
        {
            SetupTimer(connectionTimeout);
            StartConnect(true);
        }
        #endregion//connection region

        #region Set Socket Option Region
        /// <summary>
        /// sets default socket option:
        ///     ReuseAddress: 1
        ///     DontLinger: 1
        ///     Linger: false
        ///     NoDelay: 1
        /// and Property ExclusiveAddressUse to false
        /// </summary>
        private void SetDefaultSocketOptionsAndProperties()
        {
            if (socket != null)
            {
                try
                {
                    //2008/9/26 - BV - this is needed to allow us to rebind sockets
                    socket.ExclusiveAddressUse = false;

                    socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
                    //BF# 14345 - BV - 8/27/2008
                    //this is replaced by the KeepAliveTime and KeepAliveInterval properties
                    //KeepAlives will not be turned on by default to preserve current functionality
                    //socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 5000);
                    socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, 1);
                    LingerOption lingerOption = new LingerOption(false, 0);
                    socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, lingerOption);
                    socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, 1);
                }
                catch (SocketException se)
                {
                    RaiseOnSocketError(se);
                }
                catch (Exception e)
                {
                    RaiseOnGeneralError(e);
                }
            }
        }

        /// <summary>
        /// exposes the socket's SetSocketOption method
        /// </summary>
        /// <param name="optionLevel">the option level to specify</param>
        /// <param name="optionName">the option name to specify</param>
        /// <param name="optionValue">the value of the option</param>
        public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, bool optionValue)
        {
            socket.SetSocketOption(optionLevel, optionName, optionValue);
        }

        /// <summary>
        /// exposes the socket's SetSocketOption method
        /// </summary>
        /// <param name="optionLevel">the option level to specify</param>
        /// <param name="optionName">the option name to specify</param>
        /// <param name="optionValue">the value of the option</param>
        public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, byte[] optionValue)
        {
            socket.SetSocketOption(optionLevel, optionName, optionValue);
        }

        /// <summary>
        /// exposes the socket's SetSocketOption method
        /// </summary>
        /// <param name="optionLevel">the option level to specify</param>
        /// <param name="optionName">the option name to specify</param>
        /// <param name="optionValue">the value of the option</param>
        public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, int optionValue)
        {
            socket.SetSocketOption(optionLevel, optionName, optionValue);
        }

        /// <summary>
        /// exposes the socket's SetSocketOption method
        /// </summary>
        /// <param name="optionLevel">the option level to specify</param>
        /// <param name="optionName">the option name to specify</param>
        /// <param name="optionValue">the value of the option</param>
        public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, object optionValue)
        {
            socket.SetSocketOption(optionLevel, optionName, optionValue);
        }

        //BF#14345 - BV - 8/27/2008
        /// <summary>
        /// will attempt to enable or disable the TCP KeepAlive based on the KeepAliveTime and KeepAliveInterval properties
        /// </summary>
        /// <param name="enableKeepAlive">set to true to enable, set to false to disable</param>
        /// <returns>whether or not TCP keep alives are currently enabled (will return false is socket is null)</returns>
        public bool ToggleTCPKeepAlive(bool enableKeepAlive)
        {
            bool toReturn = false; //assume keepalives were not enabled

            if (enableKeepAlive)
            {
                if (keepAliveIntervalInMS > 0 && keepAliveTimeInMS > 0)//make sure both are greater than zero
                {
                    toReturn = ApplyKeepAliveValuesToSocket();//will return whether or not we could successfully apply the values to the socket
                }
                else
                {
                    //will return false since the time/interval is set to zero
                    //NOTE: if keep alives were turned on by previous code
                    //this will NOT disable them, call this method with "false" to ensure keep alives are disabled
                }
            }
            else//will return false
            {
                keepAliveIntervalInMS = 0;
                keepAliveTimeInMS = 0;
                ApplyKeepAliveValuesToSocket();
            }

            return toReturn;
        }

        //BF#14345 - BV - 8/27/2008
        /// <summary>
        /// this method sets the KeepAliveTime and KeepAliveInterval property values and attempts to enable the TCP Keep Alives if the parameters are > 0. Otherwise TCP Keep Alives are disabled
        /// </summary>
        /// <param name="time">how long the TCP stack waits on an idle connection before sending a keep alive (Set to 0 to disable keep alives)</param>
        /// <param name="interval">how long the TCP stack waits to re-send the KeepAlive when no response was received (will continue resending until retry count is up) (set to 0 to disable keep alives)</param>
        /// <returns>whether or not the values were successfully applied to the socket. (will return false on a null socket)</returns>
        public bool ToggleTCPKeepAlive(ulong time, ulong interval)
        {
            keepAliveTimeInMS = time;
            keepAliveIntervalInMS = interval;
            return ApplyKeepAliveValuesToSocket();
        }

        //BF#14345 - BV - 8/27/2008
        /// <summary>
        /// this is a private method that contains the logic to set the keep alive values on the socket
        /// http://forums.msdn.microsoft.com/en-US/netfxnetcom/thread/d5b6ae25-eac8-4e3d-9782-53059de04628/
        /// </summary>
        /// <returns>whether or not the method completed without errors</returns>
        private bool ApplyKeepAliveValuesToSocket()
        {
            bool toReturn = false;
            if (socket != null)
            {
                // "consts" to help understand calculations
                const int bytesperlong = 4; // 32 / 8
                const int bitsperbyte = 8;

                try
                {
                    // resulting structure
                    byte[] SIO_KEEPALIVE_VALS = new byte[3 * bytesperlong];

                    // array to hold input values
                    ulong[] input = new ulong[3];

                    // put input arguments in input array
                    if (keepAliveTimeInMS == 0 || keepAliveIntervalInMS == 0) // enable disable keep-alive
                        input[0] = (0UL); // off
                    else
                        input[0] = (1UL); // on

                    input[1] = (keepAliveTimeInMS); // time millis
                    input[2] = (keepAliveIntervalInMS); // interval millis

                    // pack input into byte struct
                    for (int i = 0; i < input.Length; i++)
                    {
                        SIO_KEEPALIVE_VALS[i * bytesperlong + 3] = (byte)(input[i] >> ((bytesperlong - 1) * bitsperbyte) & 0xff);
                        SIO_KEEPALIVE_VALS[i * bytesperlong + 2] = (byte)(input[i] >> ((bytesperlong - 2) * bitsperbyte) & 0xff);
                        SIO_KEEPALIVE_VALS[i * bytesperlong + 1] = (byte)(input[i] >> ((bytesperlong - 3) * bitsperbyte) & 0xff);
                        SIO_KEEPALIVE_VALS[i * bytesperlong + 0] = (byte)(input[i] >> ((bytesperlong - 4) * bitsperbyte) & 0xff);
                    }
                    // create bytestruct for result (bytes pending on server socket)
                    byte[] result = BitConverter.GetBytes(0);
                    // write SIO_VALS to Socket IOControl
                    socket.IOControl(IOControlCode.KeepAliveValues, SIO_KEEPALIVE_VALS, result);
                    toReturn = true;
                }
                catch (SocketException sockEx)
                {
                    RaiseOnSocketError(sockEx);
                    toReturn = false;

                }
                catch (Exception ex)
                {
                    RaiseOnGeneralError(ex);
                    toReturn = false;
                }
            }
            else
            {
                //socket doesn't exist... we aren't going to set anything on it
                //we will return false
            }
            return toReturn;
        }


        #endregion//set socket

        #region Raise Event Region
        /// <summary>
        /// raises the OnConnect event
        /// </summary>
        private void RaiseOnConnect()
        {
            if (OnConnect != null)
            {
                OnConnect();
            }
        }

        /// <summary>
        /// raises the OnRecieve event
        /// </summary>
        /// <param name="data">byte array of data</param>
        /// <param name="numBytes">number of bytes received</param>
        private void RaiseOnReceive(byte[] data, long numBytes)
        {
            if (OnReceive != null)
            {
                OnReceive(data, numBytes);
            }
        }

        private void RaiseOnConnectionTimeout()
        {
            if (OnConnectionTimeout != null)
            {
                OnConnectionTimeout();
            }
        }

        #endregion//raise event

        #region Receive Region
        /// <summary>
        /// begins an asynchronous receive of data
        /// </summary>
        public void StartReceive()
        {
            try
            {
                if (socket.Connected)
                {
                    StateObject state = new StateObject(receiveBufferSize);
                    state.WorkSocket = socket;
                    socket.BeginReceive(state.Buffer, 0, state.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
                }
                else
                {
                    //did not connect
                }
            }
            catch (SocketException se)
            {
                RaiseOnSocketError(se);
            }
            catch (Exception e)
            {
                RaiseOnGeneralError(e);
            }
        }

        /// <summary>
        /// Raises the OnRecieve event when asynchronous receive finishes
        /// </summary>
        /// <param name="ar">the IAsyncResult that holds the receive buffer</param>
        public void ReceiveCallback(IAsyncResult ar)
        {
            StateObject state = null;
            int bytesRead = 0;

            try
            {
                state = (StateObject)ar.AsyncState;
            }
            catch (SocketException se)
            {
                RaiseOnSocketError(se);
                RaiseOnDisconnect();
                this.Shutdown();
            }
            catch (System.Exception e)
            {
                RaiseOnGeneralError(e);
            }

            if (state != null)
            {
                try
                {
                    //this will throw a socket exception if the connection was closed (RST packet)
                    //the socket exception will be handled correctly
                    bytesRead = socket.EndReceive(ar);

                    if (bytesRead == 0)//if the connection was closed with a shutdown call (FIN/ACK packets)
                    {
                        //finish processing the shutdown on our end first
                        //before notifying subscribers that we are disconnected
                        this.Shutdown();
                        RaiseOnDisconnect();

                    }
                    else //we got data bytes
                    {
                        //BV - 2008/9/25
                        //it is useless to send the whole buffer up to subscibers
                        //we already know how many bytes we read
                        //there is no need to send a buffer full of empty bytes
                        //forcing each subsciber to cut off the empty bytes and create a new byte array
                        //we should just create the properly sized array here

                        byte[] bytesToNotifyWith = new byte[bytesRead];
                        Array.Copy(state.Buffer, bytesToNotifyWith, bytesRead);

                        //notify subscibers immediately that we received bytes
                        RaiseOnReceive(bytesToNotifyWith, bytesRead);

                        //start receiving again
                        socket.BeginReceive(state.Buffer, 0, state.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
                    }

                }
                catch (ObjectDisposedException oe)
                {
                    RaiseOnDisconnect();
                    RaiseOnGeneralError((Exception)oe);
                }
                catch (SocketException se)
                {
                    if (se.ErrorCode == CONNECTION_RESET_BY_PEER)//this happens if the socket gets closed with a RST packet
                    {
                        //finish processing the shutdown on our end first
                        //before notifying subscribers that we are disconnected
                        this.Shutdown();
                        RaiseOnDisconnect();
                    }
                    else //since we correctly handle the RST disconnection above
                    //we only need to notify of a socket error for all other error codes
                    {
                        RaiseOnSocketError(se);
                    }
                }
                catch (Exception e)
                {
                    RaiseOnGeneralError(e);
                }
            }
            else //state is null
            {
                Exception nullState = new Exception("ReceiveCallback cannot process correctly, returned state object was null");
                RaiseOnGeneralError(nullState);
            }
        }
        #endregion//receive

        #region Connection Monitor Region

        /// <summary>
        /// handles the connectionTimer's elapsed event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void connectionTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            try
            {
                if (connectionTimer != null)
                {
                    connectionTimer.Enabled = false;
                    if (connecting)
                    {
                        socket.Shutdown(SocketShutdown.Both);
                        socket.Close();
                        RaiseOnConnectionTimeout();
                    }
                }
            }
            catch (SocketException se)
            {
                RaiseOnSocketError(se);
            }
            catch (Exception ex)
            {
                RaiseOnGeneralError(ex);
            }
        }

        //2008/9/25 - BV - removing the connection timer since all disconnection notifications will be handled by ReceiveCallback method
        //        /// <summary>
        //        /// handles the connectionTimer's elapsed event
        //        /// </summary>
        //        /// <param name="sender"></param>
        //        /// <param name="e"></param>
        //        /// 
        //        private void connectionTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        //        {

        ////            log.Debug("Timer Going Off..."+count.ToString());
        //            //BV - 6/05/2008 - multithreading is incorrect and this object can be disposed 
        //            //before the logic gets to this line of code
        //            if (connectionTimer != null)
        //            {
        //                connectionTimer.Stop();
        //            }

        //            //			if (this.remoteEP != null && !IpHlpApiDotNet.IPHlpAPI32Wrapper.Connected(this.remoteEP))
        //            //			{
        //            //				this.Shutdown();
        //            //			}

        //            if (this.Connected)
        //            {
        //                currentlyConnected = true;
        //            }
        //            else
        //            {
        //                if (currentlyConnected)
        //                {
        //                    currentlyConnected = false;
        //                    RaiseOnDisconnect();
        //                }
        //            }
        //            //BV - 6/05/2008 - multithreading is incorrect and this object can be disposed 
        //            //before the logic gets to this line of code
        //            if (connectionTimer != null)
        //            {
        //                connectionTimer.Start();
        //            }
        //        }

        /// <summary>
        /// polls the socket for its current state
        /// </summary>
        /// <param name="microSeconds">time to wait for a response</param>
        /// <param name="mode">one of the System.Net.Socket.SelectMode values</param>
        /// <returns>true if connected, false if not</returns>
        public bool Poll(int microSeconds, SelectMode mode)
        {
            if (socket == null)
                return false;
            else
                return socket.Poll(microSeconds, mode);
        }
        #endregion//connection monitor

        #region IDisposable Region

        /// <summary>
        /// This method if the same as the dispose.
        /// </summary>
        public void Close()
        {
            Dispose();
        }
        /// <summary>
        /// disposes of the object
        /// </summary>
        /// 
        public override void Dispose()
        {
            try
            {
                StopClient();
            }
            catch { }
            try
            {
                //2008/9/25 - BV - removing the connection timer since all disconnection notifications will be handled by ReceiveCallback method
                //2008/12/01 - ML - adding back in for connection timeout
                if (connectionTimer != null)
                {
                    connectionTimer.Elapsed -= new System.Timers.ElapsedEventHandler(connectionTimer_Elapsed);
                    connectionTimer.Dispose();
                }
            }
            finally
            {
                //
                // Release all event Handlers DSJ 4/27/07
                //
                //2008/9/25 - BV - removing the connection timer since all disconnection notifications will be handled by ReceiveCallback method
                //2008/12/01 - ML - adding back in for connection timeout
                connectionTimer = null;
                OnConnect = null;
                OnReceive = null;
                //
                // Moved this to the bottom so that the on Disconnect Event gets Fired.
                // when the close is executed DSJ 4/27/07
                //
                base.Dispose();
            }
        }
        #endregion//IDisposable


        #region Properties Region
        /// <summary>
        /// gets/sets the underlying socket
        /// </summary>
        public Socket Socket
        {
            get
            {
                return this.socket;
            }
            set
            {
                this.socket = value;
            }
        }

        /// <summary>
        /// gets a value indicating whether the socket is in a connection state
        /// </summary>
        public bool Connecting
        {
            get { return connecting; }
        }
        #endregion//properties

        #region Send Region
        /// <summary>
        /// sends the data over the socket
        /// </summary>
        /// <param name="data">data to be sent</param>
        /// <returns>number of bytes sent; OnSocketError event raised on send failure</returns>
        public int Send(byte[] data)
        {
            return Send(data, data.Length);
        }

        /// <summary>
        /// sends the data over the socket
        /// </summary>
        /// <param name="data">data to be sent</param>
        /// <returns>number of bytes sent; OnSocketError event raised on send failure</returns>
        public int Send(string data)
        {
            byte[] byteData = Encoding.UTF8.GetBytes(data);
            return Send(byteData);
        }

        /// <summary>
        /// sends the data over the socket
        /// </summary>
        /// <param name="data">data to be sent</param>
        /// <returns>number of bytes sent; OnSocketError event raised on send failure</returns>
        public int Send(char[] data)
        {
            byte[] byteData = Encoding.UTF8.GetBytes(data);
            return Send(byteData);
        }

        /// <summary>
        /// sends the data over the socket
        /// </summary>
        /// <param name="data">data to be sent</param>
        /// <param name="length">length of data to send</param>
        /// <returns>number of bytes sent; OnSocketError event raised on send failure</returns>
        public int Send(byte[] data, int length)
        {
            int count = 0;
            try
            {
                count = socket.Send(data, length, SocketFlags.None);

                ///not sure what this was doing..maybe trying not to override a buffer
                ///changed 3/28/2007 by DJ and Ron
                //int remainingLength = length;
                //for (int i = 0; i < length; i = i + 1024)
                //{
                //    count += socket.Send(data, i, Math.Min(remainingLength, 1024), SocketFlags.None);
                //    remainingLength = Math.Max(0, remainingLength - 1024);
                //}

                //if Send returns 0 and length is greater than 0, socket is disconnected
                if (count == 0 && length > 0)
                {
                    RaiseOnDisconnect();
                }
            }
            catch (SocketException se)
            {
                RaiseOnSocketError(se);
            }
            catch (Exception e)
            {
                RaiseOnGeneralError(e);
            }

            return count;
        }

        /// <summary>
        /// begins an asynchronous send of data
        /// </summary>
        /// <param name="data">data to be sent</param>
        public void StartSend(string data)
        {
            if (socket.Connected)
            {
                byte[] byteData = Encoding.UTF8.GetBytes(data);
                socket.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), socket);
            }
        }

        /// <summary>
        /// begins an asynchronous send of data
        /// </summary>
        /// <param name="byteData">data to be sent</param>
        public void StartSend(byte[] byteData)
        {
            if (socket.Connected)
            {
                socket.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), socket);
            }
        }

        /// <summary>
        /// raises the OnSend event when asynchrounous send is complete
        /// </summary>
        /// <param name="ar">IAsyncResult object</param>
        protected void SendCallback(IAsyncResult ar)
        {
            try
            {
                int bytesSent = socket.EndSend(ar);
                RaiseOnSend(bytesSent);
            }
            catch (SocketException se)
            {
                RaiseOnSocketError(se);
            }
            catch (Exception e)
            {
                RaiseOnGeneralError(e);
            }
        }
        #endregion//send

        #region Shutdown Region
        /// <summary>
        /// shuts down and closes the socket only.
        /// </summary>
        public void StopClient()
        {
            //2008/9/25 - BV - removing the connection timer since all disconnection notifications will be handled by ReceiveCallback method
            ////BV - 6/05/2008 - disable the timer first to keep it from interfering\
            //2008/12/01 - ML - adding back in for connection timeout
            if (connectionTimer != null)
            {
                connectionTimer.Enabled = false;
            }
            Shutdown();

            try
            {
                if (socket != null)
                {
                    socket.Close();
                }
                else
                {

                }
            }
            catch { }
            //
            // Release all resources associated with that socket. DSJ 4/27/07
            //
            socket = null;
            remoteEP = null;
            localEP = null;
            RaiseOnDisconnect();
        }

        /// <summary>
        /// shuts down and closes the socket
        /// </summary>
        private void Shutdown()
        {
            try
            {
                if (socket != null)
                {
                    //BV - 6/05/2008 
                    //this library makes it nearly impossible to actually CLOSE a socket correctly...(and reopen another one immediately using the same ip/port address)
                    //Close method calls Dispose()
                    //Dispose() calls StopClient()
                    //StopClient() calls ShutDown()
                    //this method tells the socket to Shutdown
                    //--this can throw an exception indicating that the socket is disconnected
                    //this produces a call to the aSync received event handler (ReceiveCallback)
                    //on a SEPERATE THREAD
                    //so now we have two competing threads one trying to shut down the socket,
                    //while the other one is trying to process a call back
                    //not to mention the "connectiontimer" can go off at any moment
                    //and of course the calling application may be threaded as well
                    //resulting in even more threads fighting for shared (and non MUTEXed resources)
                    //--ReceiveCallback throws exceptions due to the "socket" object being set to null on other threads
                    //--ReceiveCallback tries to actually calls BeginReceive (which makes no sense while we are trying to SHUTDOWN the socket
                    //TODO: rework the way multithreading is handled in this library to ensure correct processing
                    //for now I call close instead of shutdown to ensure all resources are released
                    /***************************************/
                    //to ensure data for outgoing transmission will be sent
                    //we must temporarily set the DontLingerSocket option to false
                    //and have specified a timeout interval of 1 second for the lingeroption
                    LingerOption doLinger = new LingerOption(true, 1);
                    socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, false);
                    socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, doLinger);

                    //commented out until threading issues are resolved
                    //socket.Shutdown(SocketShutdown.Both);
                    socket.Close();


                }
            }
            //nothing to do; it's already disposed
            catch (Exception err)
            {
                //log.Error("Exception in ShutDown ", err);
            }
        }
        #endregion//shutdown

        #region Common Properties Region
        //BF#14345 - BV - 8/27/2008
        /// <summary>
        /// value specifies how long the TCP connection sits idle, with no traffic, before TCP sends a keep-alive packet. The default 5,000 milliseconds(ms) or 5 seconds.
        /// NOTE: changing this value will not automatically change the TCP KeepAlive settings on the Socket
        /// You must call the ToggleTCPKeepAlive method to apply your changes
        /// http://msdn.microsoft.com/en-us/library/ms819735.aspx
        /// </summary>
        public ulong KeepAliveTime
        {
            get
            {
                return keepAliveTimeInMS;
            }
            set
            {
                keepAliveTimeInMS = value;
            }
        }

        //BF#14345 - BV - 8/27/2008
        /// <summary>
        /// value indicates how many milliseconds to wait for a response after sending a keep-alive before repeating the keep-alive. If no response is received, the TCP/IP stack continues sending keep-alives at this interval until a response is received or until the stack reaches the packet retry limit specified in the TCPMaxDataRetransmissions registry key. KeepAliveInterval defaults to 1 second (1000 ms).
        /// NOTE: changing this value will not automatically change the TCP KeepAlive settings on the Socket
        /// You must call the ToggleTCPKeepAlive method to apply your changes
        /// http://msdn.microsoft.com/en-us/library/ms819735.aspx
        /// </summary>
        public ulong KeepAliveInterval
        {
            get
            {
                return keepAliveIntervalInMS;
            }
            set
            {
                keepAliveIntervalInMS = value;
            }
        }


        /// <summary>
        /// returns a value indicating whether the socket is connected.  
        /// NOTE: this value may not be accurate if a send or receive has not occurred
        /// </summary>
        public bool Connected
        {
            get
            {
                if (socket == null)
                {
                    return false;
                }
                else
                {
                    return socket.Connected;
                }
            }
        }

        /// <summary>
        /// gets the remote port of the client
        /// </summary>
        public int RemotePort
        {
            get
            {
                return ((IPEndPoint)socket.RemoteEndPoint).Port;
            }
        }

        /// <summary>
        /// gets the local port of the client
        /// </summary>
        public int LocalPort
        {
            get
            {
                return ((IPEndPoint)socket.LocalEndPoint).Port;
            }
        }

        /// <summary>
        /// gets the RemoteHostIP of the connection
        /// </summary>
        public string RemoteHostIP
        {
            get
            {
                return IPAddress.Parse(((IPEndPoint)socket.RemoteEndPoint).Address.ToString()).ToString();
            }
        }

        /// <summary>
        /// gets the socket's EndPoint
        /// </summary>
        public EndPoint RemoteEndPoint
        {
            get
            {
                return socket.RemoteEndPoint;
            }
        }
        #endregion//common properties
    }
    #endregion
}
